﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace MPlusTree
{
    public class MPStore
    {

        static byte[] magic = (new ASCIIEncoding()).GetBytes("MPTree data file");

        private FileStream fileHandle;
        public static int IOread;
        public static int IOwrite;
        private bool isOpen;
        private static int PAGE_SIZE = 4096;

        public MPStore(int pageSize)
        {
            SetOpen(false);
            IOread = 0;
            IOwrite = 0;
            PAGE_SIZE = pageSize;
        }

        public MPStore()
        {
            SetOpen(false);
            IOread = 0;
            IOwrite = 0;
        }

        private bool TwoArrayEquals(byte[] a, byte[] b, int len)
        {
            if ((a.Length < len) || (b.Length < len)) return false;
            for (int i = 0; i < len; ++i)
            {
                if (a[i] != b[i]) return false;
            }

            return true;
        }

        public void Create(String filename)
        {
            if (IsOpen()) return;
            fileHandle = new FileStream(filename, FileMode.OpenOrCreate);
            if (fileHandle == null) return;
            SetOpen(true);

            /* Reserve page 0 */
            byte[] page = new byte[PageSize()];

            Array.Clear(page, 0, PageSize());
            Array.Copy(magic, page, magic.Length);
            fileHandle.Write(page, 0, PageSize());
        }
        
        public void Open(String filename)
        {
            if (IsOpen()) return;
            fileHandle = new FileStream(filename, FileMode.Open);
            if (fileHandle == null) return;

            // Verify that the magic words are there
            byte[] page = new byte[PageSize()];
            fileHandle.Read(page, 0, PageSize());
            if (!TwoArrayEquals(page, magic, magic.Length))
            {
                fileHandle.Close();
                return;
            }

            SetOpen(true);
        }


        public void Close()
        {
            if (!IsOpen()) return;
            fileHandle.Close();
            SetOpen(false);
        }

        public void Read(ulong page, byte[] buf)
        {
            if (IsOpen())
            {
                fileHandle.Seek((long)(page *(ulong) PageSize()), SeekOrigin.Begin);
                fileHandle.Read(buf, 0, PageSize());
                IOread++;
            }
        }

        public void Write(ulong page, byte[] buf)
        {
            if (IsOpen())
            {
                fileHandle.Seek((long)(page * (ulong)PageSize()), SeekOrigin.Begin);
                fileHandle.Write(buf, 0, PageSize());
                IOwrite++;
            }
        }

        public ulong Allocate()
        {
            if (!IsOpen()) return (0);
            // See if there's a deleted page
            byte[] buf = new byte[PageSize()];
            Read(0, buf);

            byte[] pageBytes = new byte[sizeof(ulong)];
            Array.Copy(buf, magic.Length, pageBytes, 0, sizeof(ulong));
            ulong page = BitConverter.ToUInt64(pageBytes, 0);
            if (page != 0)
            {
                // Reclaim this page
                Read(page, buf);
                Write(0, buf);
            }
            else
            {
                page = (ulong)(fileHandle.Seek(0, SeekOrigin.End) / PageSize());
                for (int i = 0; i < PageSize(); i++)
                    buf[i] = 0;
                fileHandle.Write(buf, 0, PageSize());
            }

            return page;
        }

        public void Deallocate(ulong page)
        {
            byte[] temp = new byte[sizeof(ulong)];
            if (!IsOpen()) return;
            // Get the old head of the list
            byte[] buf = new byte[PageSize()];
            Read(0, buf);
            Array.Copy(buf, magic.Length, temp, 0, sizeof(ulong));
            // Write the new head of the list
            Array.Copy(BitConverter.GetBytes(page), 0, buf, magic.Length, sizeof(ulong));
            Write(0, buf);
            // In our new head, put link to old head
            Array.Copy(temp, 0, buf, magic.Length, sizeof(ulong));
            Write(page, buf);
        }

        public int PageSize()
        {
            return PAGE_SIZE;
        }

        public static int GetPageSize()
        {
            return PAGE_SIZE;
        }

        public bool IsOpen()
        {
            return isOpen;
        }
        
        private void SetOpen(bool o)
        {
            isOpen = o;
        }
    }
}
